/****************************************************************************
 * charger.c
 *
 * Copyright (c) 2011 Quanta Computer
 * Copyright (c) 2011 Laptop per Child
 *
 * This file handles the Intersil 9519 charger chip.
 *
 ***************************************************************************/
#include <stdio.h>
#include "cc_compat.h"
#include "charger.h"
#include "gpio_smbus.h"
#include "mppt.h"
#include "battery.h"
#include "timer.h"


// values in mA or mV.

// We do not want to round the input current computations anymore
// since we need the input current to be <= to the provided setting
// and never higher.
#define mA_TO_INPUT_CURRENT(mA)  ((mA*2) & ~0x7f)
#define INPUT_CURRENT_TO_mA(IC)	 (IC/2)

// The actual current into the charger is 1/2 of the setting below due
// to the value of the sense resistor
#define MAX_INPUT_CURRENT   ((2048*2) & ~0x7f) // low 7 bits ignored

// Maximum current to allow for charging the battery.
#define MAX_CHARGE_CURRENT  ((3072 + 0x40) & ~0x7f) // low 7 bits ignored
// Upper bound on system voltage. This is also the point at which the
// charger chagnes from constant current charging to constant voltage.

// CV for the battery is 7.4V This needs to be slightly higher so that the
// battery voltage actually gets to that voltage
#define MAX_SYSTEM_VOLTAGE  ((7440 + 0x08) & ~0x0f) // low 4 bits ignored
// System voltage threshold for trickle charge.
#define MIN_SYSTEM_VOLTAGE  ((5000 + 0x80) & ~0xff) // low 8 bits ignored

// commands to the chip
#define ChargeCurrent_CMD   0x014
#define MaxVoltage_CMD      0x015
#define Control_CMD         0x03d
#define MinVoltage_CMD      0x03e
#define InputCurrent_CMD    0x03f

#define ManufacturerID_CMD  0x0fe
#define	DefaultManuID       0x0049
#define DeviceID_CMD        0x0ff
#define DefaultDeviceID     0x0003


#define CTL_SGATEON 	(1<<0)		// 1 forces SGATE FET on.  0 allows ISL9519 to
//									// turn it off if adapter current below min threshold
#define CTL_80kHz		(1<<1)		// When in variable freq mode, 1 sets min freq to 80kHz.
//                                  // 80KHz = 0 sets min frequency to 140kHz
#define CTL_Isolate_Adapter (1<<2)	// Isolate_Adapter = 1 disconnects the adapter from the
//                                  // charger by making the AGATE pin HI Z. Default 0
#define CTL_VariableFreq (1<<3)		// 0: Forces fixed frequency operation of the buck regulator
//                                  // 1: Allows variable frequency
#define CTL_LowPower	(1<<4)		// 1: removes power from the battery discharge monitor
//                                  // circuits to reduce power consumption. Default 0
#define CTL_SelVFBit	(1<<5)		// 0: Listens to VFSW pin; 1:Listens to VariableFreqBit
#define CTL_AC_OK		(1<<6)		// Read only
#define CTL_Trickle 	(1<<7)		// Read only

// charging will terminate (BGATE FET turns off) if we don't
// write to MaxSystemVoltage or ChargeCurrent within 175 seconds.
static int charger_watchdog;
static int charger_timer;
static unsigned int last_input_current;
static unsigned int last_charge_current;
static unsigned int max_input_current;

bool Write_ReadBack(unsigned char input_cmd, unsigned int input_value)
{
	unsigned char i;
	unsigned int gotback, r;

	for (i = 0; i < 3; i++)
	{
		r = Write_Charger(input_cmd, input_value);
		gotback = Read_Charger(input_cmd);
		if ( r == 0 && gotback == input_value)
		{
#if 0
			print_tstamp();
			printf("write isl val 0x%x to reg 0x%x\n",
				   input_value, (int)input_cmd);
#endif
			return 0;
		}
		printf("charger wrote 0x%x to 0x%x got 0x%x, r=%d try %d\n",
			   input_value, (int)input_cmd, gotback, r,(int)i);
	}
	return 1;
}

bool Check_ISL_ID(void)
{
	unsigned int id;
	unsigned char i;

	for (i = 0; i < 3; i++)
	{
		id = Read_Charger(DeviceID_CMD);
		if (id == DefaultDeviceID)
			return 1;
		printf("wrong ISL ID %04x\n", id);
	}
	puts("ISL ID check failed");
	return 0;

}

int power_limit_to_input_current(int pl)
{
	int ic;

	// the older non-ISL charger uses all 8 bits of power_limit
	// for PWM control.  we can only use 6 bits, with a maximum
	// value of 0x20 (which becomes 0x1000 in the InputCurrent
	// register).
	ic = (pl << 7);
	if (ic > max_input_current) ic = max_input_current;

	return ic;
}

unsigned char Init_CHGRegister()
{
	unsigned char result=0;

	result |= Write_ReadBack(InputCurrent_CMD, max_input_current);
	result <<= 1;
	result |= Write_ReadBack(MinVoltage_CMD, MIN_SYSTEM_VOLTAGE);
	result <<= 1;
	result |= Write_ReadBack(MaxVoltage_CMD, MAX_SYSTEM_VOLTAGE);
	result <<= 1;
	result |= Write_ReadBack(ChargeCurrent_CMD, MAX_CHARGE_CURRENT);

	if (result == 0 )
	{
		last_input_current  = max_input_current;
		// Init this to an impossible value so that the first
		// current change always happens.
		last_charge_current = MAX_CHARGE_CURRENT;
	}

	return result;
}

unsigned char ISL_charger_init(void)
{
	unsigned char result=0;
	unsigned char err;

	max_input_current = MAX_INPUT_CURRENT;

	if (!Check_ISL_ID() )
	{
		printf("!!! Error getting ISL charger ID");
		result |= 0x01;
	}

	err = Init_CHGRegister();
	if ( err )
	{
		printf("!!! Error setting initial ISL values Err=%x\n",(uint)err);
		result |= 0x02;
	}

	return result;
}

void ISL_charger_keepalive(void)
{
	if (check_timer(charger_watchdog, 30 * 1000))
	{
		Write_ReadBack(MaxVoltage_CMD, MAX_SYSTEM_VOLTAGE);
		charger_watchdog = get_ms_timer();
	}
}

bool ISL_is_tricklecharging(void)
{
	unsigned int cctl;

	cctl = Read_Charger(Control_CMD);
	return 	(cctl & CTL_Trickle) != 0;
}

bool ISL_set_input_current_full(void)
{
	bool result;

	result = Write_ReadBack(InputCurrent_CMD, max_input_current);
	if (result)
	{
		last_input_current = max_input_current;
	}
	return result;
}

bool ISL_set_input_current(unsigned int mA)
{
	int ic;
	bool result;

	ic = mA_TO_INPUT_CURRENT(mA);
	if (ic > max_input_current)
	{
		ic = max_input_current;
	}
	result = Write_ReadBack(InputCurrent_CMD, ic);
	if (result) last_input_current = ic;
	return result;
}

// Legacy function used on systems without MPPT v2
bool ISL_set_power_limit_v1(int pl)
{
	static unsigned int last_power_limit;
	int ic;

	if (last_power_limit != pl)
	{
		last_power_limit =  pl;
		ic = power_limit_to_input_current(pl);
		return Write_ReadBack(InputCurrent_CMD, ic);
	}
	return 1;
}

bool ISL_set_charge_current(unsigned int cc)
{

	if (last_charge_current != cc)
	{
		last_charge_current = cc;
		charger_watchdog = get_ms_timer();
		return Write_ReadBack(ChargeCurrent_CMD, cc);
	}
	return 1;
}

bool ISL_charger_onoff(int onoff)
{
	return ISL_set_charge_current(onoff ? MAX_CHARGE_CURRENT : 0);
}

#ifdef UNUSED
unsigned int ISL_get_charge_current(void)
{
	return Read_Charger(ChargeCurrent_CMD);
}

unsigned int ISL_read_input_current(void)
{
	return INPUT_CURRENT_TO_mA(Read_Charger(InputCurrent_CMD));
}

unsigned int ISL_get_last_input_current(void)
{
	return INPUT_CURRENT_TO_mA(last_input_current);
}
#endif

unsigned int ISL_get_max_input_current(void)
{
	return INPUT_CURRENT_TO_mA(max_input_current);
}

unsigned int ISL_set_max_input_current(unsigned int mA)
{
	int ic;

	ic = mA_TO_INPUT_CURRENT(mA);
	if (ic > MAX_INPUT_CURRENT)
	{
		ic = MAX_INPUT_CURRENT;
	}
	max_input_current = ic;

	// When the max is updated the existing input current has
	// to be re-evaluated and reduced if needed.
	if (last_input_current > max_input_current)
	{
		ISL_set_input_current(max_input_current);
	}

	return INPUT_CURRENT_TO_mA(max_input_current);
}

unsigned int ISL_mA_to_input_current(unsigned int mA)
{
	unsigned int ic;

	ic = mA_TO_INPUT_CURRENT(mA);

	if (ic > MAX_INPUT_CURRENT)
	{
		ic = MAX_INPUT_CURRENT;
	}

	return INPUT_CURRENT_TO_mA(ic);
}
